// Copyright 2009 Ivan Wong. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package provides the FastCGI application interface
// implementation for GO.
// Heavily inspired by fcgigo: git://github.com/jldailey/fcgigo.git
// Many ideas are directly borrowed from it
package fastcgi

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"io"
	"log"
	"net"
	"os"
)

const (
	FCGI_BEGIN_REQUEST     = 1
	FCGI_ABORT_REQUEST     = 2
	FCGI_END_REQUEST       = 3
	FCGI_PARAMS            = 4
	FCGI_STDIN             = 5
	FCGI_STDOUT            = 6
	FCGI_STDERR            = 7
	FCGI_DATA              = 8
	FCGI_GET_VALUES        = 9
	FCGI_GET_VALUES_RESULT = 10
	FCGI_UNKNOWN_TYPE      = 11
	FCGI_MAXTYPE           = FCGI_UNKNOWN_TYPE
)

const (
	FCGI_KEEP_CONN = 1
)

const (
	FCGI_REQUEST_COMPLETE = 0
	FCGI_CANT_MPX_CONN    = 1
	FCGI_OVERLOADED       = 2
	FCGI_UNKNOWN_ROLE     = 3
)

type Error struct {
	string
}

var ErrInvalidPacket os.Error = os.NewError("invalid packet")

type Application interface {
	Handle(r *Request) bool
}

type Header struct {
	Version       uint8
	Type          uint8
	RequestId     uint16
	ContentLength uint16
	PaddingLength uint8
	Reserved      uint8
}

func (h *Header) bytes() []byte {
	var b [8]byte
	b[0] = h.Version
	b[1] = h.Type
	binary.BigEndian.PutUint16(b[2:4], h.RequestId)
	binary.BigEndian.PutUint16(b[4:6], h.ContentLength)
	b[6] = h.PaddingLength
	b[7] = 0
	return b[:]
}

type BeginRequestBody struct {
	Role     uint16
	Flags    uint8
	Reserved [5]byte
}

type EndRequestBody struct {
	AppStatus      uint32
	ProtocolStatus byte
	Reserved       [3]byte
}

func (e *EndRequestBody) bytes() []byte {
	var b [8]byte
	binary.BigEndian.PutUint32(b[0:4], e.AppStatus)
	b[4] = e.ProtocolStatus
	return b[:]
}

type packet struct {
	hdr     Header
	content []byte
}

func newPacket(kind uint8, reqid uint16, b []byte) *packet {
	p := new(packet)
	n := len(b)
	p.hdr = Header{
		Version: 1,
		Type: kind,
		RequestId: reqid,
		ContentLength: uint16(n),
		PaddingLength: uint8(-n & 7),
	}
	p.content = b
	return p
}

func (c *context) readPacket() (*packet, os.Error) {
	rd := c.rd
	p := new(packet)
	e := binary.Read(rd, binary.BigEndian, &p.hdr)
	if e != nil {
		return nil, e
	}
	if p.hdr.ContentLength > 0 {
		p.content = make([]byte, p.hdr.ContentLength)
		n, e := rd.Read(p.content)
		if n != int(p.hdr.ContentLength) {
			return nil, e
		}
	}
	if p.hdr.PaddingLength > 0 {
		n, e := rd.Read(make([]byte, p.hdr.PaddingLength))
		if n != int(p.hdr.PaddingLength) {
			return nil, e
		}
	}
	return p, nil
}

func (p *packet) bytes() []byte {
	hdr := p.hdr.bytes()
	hl := len(hdr)
	cl := int(p.hdr.ContentLength)
	buf := make([]byte, hl+cl+int(p.hdr.PaddingLength))
	copy(buf[0:hl], hdr)
	copy(buf[hl:hl+cl], p.content)
	return buf
}

type writer struct {
	req  *Request
	kind uint8
}

func (w *writer) Write(p []byte) (n int, err os.Error) {
	pkt := newPacket(w.kind, w.req.id, p)
	n, e := w.req.c.conn.Write(pkt.bytes())
	n -= 8 + int(pkt.hdr.PaddingLength)
	if n < 0 {
		n = 0
	}
	return n, e
}

type bufWriter struct {
	*bufio.Writer
	raw  *writer
	used bool
}

func (w *bufWriter) Write(p []byte) (n int, err os.Error) {
	w.used = true
	return w.Writer.Write(p)
}

func (w *bufWriter) close() (err os.Error) {
	w.Flush()
	n, e := w.raw.Write([]byte{})
	if n != 8 {
		return io.ErrShortWrite
	}
	return e
}

type reader struct {
	req *Request
}

func (r *reader) Read(p []byte) (int, os.Error) {
	req := r.req
	n := len(p)
	nn := 0

	for n > 0 {
		if req.ineof {
			return nn, os.EOF
		}
		m := 0
		if req.inbuf != nil {
			m = len(req.inbuf)
		}
		if m > 0 {
			x := n
			if m < x {
				x = m
			}
			copy(p[nn:nn+x], req.inbuf[0:x])
			nn += x
			req.inbuf = req.inbuf[x:]
			n -= x
		} else {
			_, e := req.c.readAndProcessPacket()
			if e != nil {
				return nn, e
			}
		}
	}

	return nn, nil
}

type bufReader struct {
	*bufio.Reader
}

type Request struct {
	Role     uint16
	keepConn bool
	id       uint16
	Params   map[string]string
	ready    bool
	c        *context
	Stdout   *bufWriter
	Stderr   *bufWriter
	Stdin    *bufReader
	inbuf    []byte
	ineof    bool
}

func newRequest(id uint16, c *context, b *BeginRequestBody) *Request {
	r := &Request{
		id: id,
		Params: make(map[string]string),
		Role: b.Role,
		keepConn: b.Flags&FCGI_KEEP_CONN != 0,
		ready: false,
		c: c,
	}
	r.Stdout = r.newBufWriter(FCGI_STDOUT, 8192)
	r.Stderr = r.newBufWriter(FCGI_STDERR, 512)
	r.Stdin = r.newBufReader()
	return r
}

func (r *Request) newBufReader() *bufReader {
	br := new(bufReader)
	rd := reader{req: r}
	br.Reader = bufio.NewReader(&rd)
	return br
}

func (r *Request) newBufWriter(kind uint8, size int) *bufWriter {
	bw := new(bufWriter)
	w := writer{
		req: r,
		kind: kind,
	}
	var e os.Error
	bw.Writer, e = bufio.NewWriterSize(&w, size)
	bw.raw = &w
	if e != nil {
		log.Fatal("failed to allocate buffer io")
	}
	return bw
}

func (r *Request) end() {
	r.Stdout.close()
	if r.Stderr.used {
		r.Stderr.close()
	}
	e := EndRequestBody{
		AppStatus: 0,
		ProtocolStatus: FCGI_REQUEST_COMPLETE,
	}
	r.c.conn.Write(newPacket(FCGI_END_REQUEST, r.id, e.bytes()).bytes())
}

func readLen(s []byte) (int, []byte, os.Error) {
	var output int
	var n int
	if len(s) < 1 {
		return 0, nil, ErrInvalidPacket
	}
	b := s[0]
	if b&0x80 != 0 {
		if len(s) < 4 {
			return 0, nil, ErrInvalidPacket
		}
		output = ((int(b) & 0x7f) << 24) + (int(s[1]) << 16) + (int(s[2]) << 8) + int(s[3])
		n = 4
	} else {
		output = int(b)
		n = 1
	}
	return output, s[n:len(s)], nil
}

func readString(s []byte, l int) (string, []byte, os.Error) {
	if len(s) < l {
		return "", nil, ErrInvalidPacket
	}
	return string(s[0:l]), s[l:len(s)], nil
}

func readParams(m map[string]string, p *packet) os.Error {
	s := p.content[0:len(p.content)]
	for len(s) > 0 {
		var (
			nlen  int
			vlen  int
			name  string
			value string
			e     os.Error
		)
		nlen, s, e = readLen(s)
		if e != nil {
			return e
		}
		vlen, s, e = readLen(s)
		if e != nil {
			return e
		}
		name, s, e = readString(s, nlen)
		if e != nil {
			return e
		}
		value, s, e = readString(s, vlen)
		if e != nil {
			return e
		}
		m[name] = value
	}
	return nil
}

type server struct {
	reqmap      map[uint16]*Request
	connChannel chan io.ReadWriteCloser
	app         Application
}

func newServer(app Application) *server {
	c := new(server)
	c.reqmap = make(map[uint16]*Request)
	c.connChannel = make(chan io.ReadWriteCloser)
	c.app = app
	return c
}

func (c *context) processPacket(p *packet) (*Request, os.Error) {
	var req *Request

	reqmap := c.s.reqmap
	reqid := p.hdr.RequestId
	if reqid != 0 {
		req, _ = reqmap[reqid]
		if req == nil && p.hdr.Type != FCGI_BEGIN_REQUEST {
			log.Printf("invalid request id %d", reqid)
			return nil, ErrInvalidPacket
		}
	}
	switch p.hdr.Type {
	case FCGI_BEGIN_REQUEST:
		var body BeginRequestBody
		binary.Read(bytes.NewBuffer(p.content), binary.BigEndian, &body)
		if req != nil {
			log.Printf("web server tried to begin request %d which is already active", reqid)
			return nil, ErrInvalidPacket
		}
		req = newRequest(reqid, c, &body)
		reqmap[reqid] = req
	case FCGI_PARAMS:
		if p.hdr.ContentLength > 0 {
			e := readParams(req.Params, p)
			if e != nil {
				log.Printf("failed to read parameters")
				return nil, ErrInvalidPacket
			}
		} else {
			req.ready = true
		}
	case FCGI_STDIN:
		if p.hdr.ContentLength > 0 {
			req.inbuf = p.content[0:]
		} else {
			req.ineof = true
		}
	default:
		log.Printf("%d %d %d\n", p.hdr.RequestId, p.hdr.Type, p.hdr.ContentLength)
	}

	if req != nil && req.ready {
		return req, nil
	}

	return nil, nil
}

func (c *context) readAndProcessPacket() (*Request, os.Error) {
	var req *Request
	p, e := c.readPacket()
	if e == nil {
		req, e = c.processPacket(p)
	}
	if e != nil {
		log.Printf("invalid packet, close connection")
		c.conn.Close()
		c.conn = nil
	}
	return req, e
}

type context struct {
	conn io.ReadWriteCloser
	rd   io.Reader
	s    *server
}

func (s *server) handleConnection(quit chan bool) {
	c := &context{s: s}
	for {
		if c.conn == nil {
			c.conn = <-s.connChannel
			c.rd = bufio.NewReader(c.conn)
		}
		req, e := c.readAndProcessPacket()
		if e != nil {
			continue
		}
		if req != nil {
			s.app.Handle(req)
			req.end()
			if !req.keepConn {
				c.conn.Close()
				c.conn = nil
			}
			s.reqmap[req.id] = nil
		}
	}
}

func (s *server) serve(l net.Listener) os.Error {
	var quits [1]chan bool
	for i := 0; i < 1; i++ {
		quit := make(chan bool)
		go s.handleConnection(quit)
		quits[i] = quit
	}
	for {
		conn, e := l.Accept()
		if e != nil {
			return e
		}
		s.connChannel <- conn
	}
	for i := 0; i < 1; i++ {
		quits[i] <- true
	}
	log.Fatal("not reached")
	return nil
}

// Run as an external FastCGI Server.
// Bind at TCP address addr to which webserver
// will try to connect to.
//
// This is useful for debugging/development
// purpose as the application will be separated
// from the web server.
func RunStandalone(addr string, app Application) os.Error {
	l, e := net.Listen("tcp", addr)
	if e != nil {
		return e
	}
	s := newServer(app)
	e = s.serve(l)
	l.Close()
	return e
}
